#pragma once

#include <vector>
#include <xtd/exception>
#include "typelib/span.h"
#include "icache/footage.h"
#include "mswin/timing.h"
#include "param.h"
#include "twomasks.h"

namespace xml { class stream; }

namespace trm {

typedef std::vector<twomasks_sptr> masks_vector;

enum direction { backward, forward };

struct exception : xtd::exception {
	exception()	throw() { }
	exception(const std::string & msg) throw() : xtd::exception(msg)	{ }
};

struct no_page_mem : exception {
	no_page_mem() : exception(
		"Not enough page memory available.\n"
		"Please, close other tasks running or enlarge system's pagefile.")
		{ }
};

struct no_virt_mem : exception {
	no_virt_mem() : exception(
		"The process exceeded all its virtual memory, sorry.")
		{ }
};

class segment
{
public:
	segment(int org = -1, icache::footage_sptr const & f = icache::footage_sptr());
	~segment();

	///erases all data and sets new origin
	void reset(int org, icache::footage_sptr const & f);
	///don't changes origin and footage
	void clear();

	int origin() const { return _origin; }
	icache::footage_sptr const & footage() const { return _footage; }

	///this is span for which masks are stored in segment,
	///you must insure that accessible_span includes request span in calc(...)
	span const & accessible_span() const { return _accessible_span; }
	void accessible_span(span const & aspan);
	void accessible_span_include(span const & inc) { accessible_span(_accessible_span.uniting(inc)); }

	trm::param const & param() const { return _param; }
	///if parameters are changed, then all masks (or smooth masks only) are erased and true is returned,
	///if parameters are actually not changed then false is returned
	bool param(trm::param p);

	const bit_mask & key_mask() const { return _key_mask; }
	void key_mask(const bit_mask &);

	bool has_frame_masks(int n) const { return _masks[n - _accessible_span.first]; } 
	twomasks_const_sptr const frame_masks(int n) const { return _masks[n - _accessible_span.first]; } 
	bool has_bit_mask(int n) const { return _accessible_span.inside(n) && has_frame_masks(n) && !frame_masks(n)->b.empty(); }
	trm::bit_mask const & bit_mask(int n) { return frame_masks(n)->b; } 
	bool has_grey_mask(int n) const { return _accessible_span.inside(n) && has_frame_masks(n) && !frame_masks(n)->g.empty(); }
	void grey_mask(int n, trm::grey_mask & m) const { return frame_masks(n)->g.extract(m); } 
	bool has_contour(int n) const { return _accessible_span.inside(n) && has_frame_masks(n) && !frame_masks(n)->contour.empty(); }
	bezier_contour const & contour(int n) { return frame_masks(n)->contour; } 

	///relative segment time to absolute composition time
	int time(direction dir, int offset) const { return _origin + (dir == backward ? -offset : +offset); }
	int segtime(int time, direction * dir = 0) const;

	///returns data span, in each frame of which non-empty bit mask is available
	span const & bit_masks_span() const { return _bit_masks_span; }
	///returns data span, in each frame of which non-empty grey (and bit) mask is available;
	///grey_masks_span may be empty unlike bit_masks_span
	span const & grey_masks_span() const { return _grey_masks_span; }
	span const & calced_masks_span() const
		{ return _param.smooth_mask ? grey_masks_span() : bit_masks_span(); }
	///ereases all masks outside of given span, do not change accessible_span
	void trim(span const & t); 

	xml::stream & serialize(xml::stream & s);

public:
	///calculates masks in frames of request, where they are empty
	void calc(bool key_mask_refined, span const & request);

	mswin::timing_tree const & timing_tree() const { return _timing; }

protected: //subclasses can overload the function to be informed about calc progress
	virtual void frame_done(int frame, direction dir, span const & calced_span) { }
	//calc(...) periodically requests this method, if it's true then calc(...) returns before work is done
	virtual bool is_canceled() const { return false; }

private:
	struct calc_data;
	void calc(calc_data & d);
	void check_mem();
	void decrease_mask_freedom(twomasks & m, bezier_contour const * etalone = 0);
	void smooth_mask(twomasks_sptr const & p, byte_image const & im);
	icache::cached_image_sptr load_origin_frame(span request);
	icache::cached_image_sptr load_frame(int frame, direction following);
	void set_frame_masks(int n, twomasks_sptr const & p) { _masks[n - _accessible_span.first] = p; }
	void reset_frame_masks(int n) { _masks[n - _accessible_span.first].reset(); }
	bool has_origin_masks() const { return has_frame_masks(_origin); } 
	twomasks_const_sptr const origin_masks() const { return frame_masks(_origin); } 
	void set_origin_masks(twomasks_sptr const & p) { set_frame_masks(_origin, p); }
	void find_bit_masks_span();
	void find_grey_masks_span();

private:
	mswin::timing_tree _timing;
	int _origin;
	span _accessible_span;
	span _bit_masks_span;
	span _grey_masks_span;
	masks_vector _masks;
	trm::bit_mask _key_mask;
	trm::param _param;
	icache::footage_sptr _footage;

private:
	friend class target_frame;
};

inline xml::stream & operator || (xml::stream & s, segment & g)
	{ return g.serialize(s); }

//inlines

inline int segment::segtime(int time, direction * dir) const {
	if (time >= _origin) {
		if (dir) *dir = forward;
		return time - _origin;
	} else {
		if (dir) *dir = backward;
		return _origin - time;
	}
}

} //namespace trm
